package com.ztomorrow.ying_cun_plus.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ztomorrow.ying_cun_plus.auth.UserHolder;
import com.ztomorrow.ying_cun_plus.constant.front.ErrorCodeEnum;
import com.ztomorrow.ying_cun_plus.dao.mapper.*;
import com.ztomorrow.ying_cun_plus.dao.po.*;
import com.ztomorrow.ying_cun_plus.dto.AuthorInfoDto;
import com.ztomorrow.ying_cun_plus.dto.req.*;
import com.ztomorrow.ying_cun_plus.dto.resp.*;
import com.ztomorrow.ying_cun_plus.manager.*;
import com.ztomorrow.ying_cun_plus.service.BookService;
import com.ztomorrow.ying_cun_plus.utils.CacheUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ztomorrow.ying_cun_plus.constant.front.CacheConstant.*;

@Service
@RequiredArgsConstructor
public class BookServiceImpl implements BookService {

    private final BookInfoMapper bookInfoMapper;
    private final BookCategoryMapper bookCategoryMapper;
    private final BookChapterMapper bookChapterMapper;
    private final BookContentMapper bookContentMapper;
    private final BookCommentMapper bookCommentMapper;
    private final UserInfoMapper userInfoMapper;
    private final AuthorServiceImpl authorService;
    private final CacheUtils cacheUtils;
    private final BookInfoCacheManager bookInfoCacheManager;
    private final BookContentCacheManager bookContentCacheManager;
    private final BookChapterCacheManager bookChapterCacheManager;
    private final AmqpMsgManager amqpMsgManager;
    private final ClearCache clearCache;
    private static final Integer REC_BOOK_COUNT = 4;

    @Override
    public RestResp<List<BookRankRespDto>> listVisitRankBooks() {
        List<BookRankRespDto> list = listRankBooks(
                CAFFEINE_VISIT_RANK, REDIS_VISIT_RANK, REDIS_VISIT_RANK_TTL,
                new LambdaQueryWrapper<BookInfo>()
                .orderByDesc(BookInfo::getVisitCount));
        return RestResp.ok(list);
    }

    @Override
    public RestResp<List<BookRankRespDto>> listNewestRankBooks() {
        List<BookRankRespDto> list = listRankBooks(
                CAFFEINE_NEWEST_RANK, REDIS_NEWEST_RANK, REDIS_NEWEST_RANK_TTL,
                new LambdaQueryWrapper<BookInfo>()
                .gt(BookInfo::getWordCount, 0)
                .orderByDesc(BookInfo::getCreateTime));
        return RestResp.ok(list);
    }

    @Override
    public RestResp<List<BookRankRespDto>> listUpdateRankBooks() {
        List<BookRankRespDto> list = listRankBooks(
                CAFFEINE_UPDATE_RANK, REDIS_UPDATE_RANK, REDIS_UPDATE_RANK_TTL,
                new LambdaQueryWrapper<BookInfo>()
                .gt(BookInfo::getWordCount, 0)
                .orderByDesc(BookInfo::getUpdateTime));
        return RestResp.ok(list);
    }

    @Override
    public RestResp<List<BookCategoryRespDto>> listCategory(Integer workDirection) {
        List<BookCategoryRespDto> cache = cacheUtils.getCache(
                obj -> BeanUtil.copyProperties(obj, BookCategoryRespDto.class),
                CAFFEINE_WORK_DIRECTION + workDirection,
                REDIS_WORK_DIRECTION + workDirection,
                jsonStr -> JSONUtil.toBean(jsonStr, BookCategoryRespDto.class));
        if (cache != null) return RestResp.ok(cache);
        List<BookCategory> list = new LambdaQueryChainWrapper<>(bookCategoryMapper)
                .eq(BookCategory::getWorkDirection, workDirection)
                .list();
        List<BookCategoryRespDto> bookCategoryRespDto =
                BeanUtil.copyToList(list, BookCategoryRespDto.class);
        cacheUtils.saveCache(bookCategoryRespDto,
                CAFFEINE_WORK_DIRECTION +  workDirection,
                REDIS_WORK_DIRECTION +  workDirection, REDIS_WORK_DIRECTION_TTL,
                JSONUtil::toJsonStr);
        return RestResp.ok(bookCategoryRespDto);
    }

    @Override
    public RestResp<BookInfoRespDto> getBookById(Long bookId) {
        return RestResp.ok(bookInfoCacheManager.getBookInfo(bookId));
    }

    @Override
    public RestResp<List<BookInfoRespDto>> listRecBooks(Long bookId) throws NoSuchAlgorithmException {
        Long categoryId = bookInfoCacheManager.getBookInfo(bookId).getCategoryId();
        List<Long> lastUpdateIdList = cacheUtils.getCache(
                obj -> (Long) obj,
                CAFFEINE_REC_BOOKS, REDIS_REC_BOOKS,
                Long::parseLong);
        if (lastUpdateIdList == null) {
            lastUpdateIdList = new LambdaQueryChainWrapper<>(bookInfoMapper)
                    .eq(BookInfo::getCategoryId, categoryId)
                    .gt(BookInfo::getWordCount, 0)
                    .orderByDesc(BookInfo::getLastChapterUpdateTime)
                    .last("limit 500")
                    .list()
                    .stream().map(BookInfo::getId).toList();
            cacheUtils.saveCache(
                    lastUpdateIdList,
                    CAFFEINE_REC_BOOKS ,
                    REDIS_REC_BOOKS , REDIS_REC_BOOKS_TTL,
                    String::valueOf);
        }
        List<BookInfoRespDto> respDtoList = new ArrayList<>();
        List<Integer> recIdIndexList = new ArrayList<>();
        int count = 0;
        Random rand = SecureRandom.getInstanceStrong();
        while (count < REC_BOOK_COUNT) {
            int recIdIndex = rand.nextInt(lastUpdateIdList.size());
            if (!recIdIndexList.contains(recIdIndex)) {
                recIdIndexList.add(recIdIndex);
                Long id = lastUpdateIdList.get(recIdIndex);
                if (bookId.equals(id)) continue;
                BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(id);
                respDtoList.add(bookInfo);
                count++;
            }
        }
        return RestResp.ok(respDtoList);
    }

    @Override
    public RestResp<BookChapterAboutRespDto> getLastChapterAbout(Long bookId) {
        // 查询小说信息
        BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(bookId);
        // 查询最新章节信息
        BookChapterRespDto bookChapter = bookChapterCacheManager.getChapter( bookInfo.getLastChapterId());
        // 查询章节内容
        String content = bookContentCacheManager.getBookContent(bookInfo.getLastChapterId());

        // 查询章节总数
        Long chapterTotal = new LambdaQueryChainWrapper<>(bookChapterMapper)
                .eq(BookChapter::getBookId, bookId)
                .count();

        // 组装数据并返回
        return RestResp.ok(BookChapterAboutRespDto.builder()
                .chapterInfo(bookChapter)
                .chapterTotal(chapterTotal)
                .contentSummary(content.substring(0, 30))
                .build());
    }

    @Override
    public RestResp<List<BookChapterRespDto>> listChapters(Long bookId) {
        List<BookChapter> list = new LambdaQueryChainWrapper<>(bookChapterMapper)
                .eq(BookChapter::getBookId, bookId)
                .orderByAsc(BookChapter::getChapterNum)
                .list();
        return RestResp.ok(BeanUtil.copyToList(list, BookChapterRespDto.class));
    }

    @Override
    public RestResp<BookCommentRespDto> listNewestComments(Long bookId) {
        // 查询评论总数
        Long commentTotal = new LambdaQueryChainWrapper<>(bookCommentMapper)
                .eq(BookComment::getBookId, bookId)
                .count();
        BookCommentRespDto bookCommentRespDto = BookCommentRespDto.builder()
                .commentTotal(commentTotal).build();
        if (commentTotal > 0) {
            // 查询最新的评论列表
            List<BookComment> bookComments = new LambdaQueryChainWrapper<>(bookCommentMapper)
                    .eq(BookComment::getBookId, bookId)
                    .orderByDesc(BookComment::getCreateTime)
                    .last("limit 5")
                    .list();

            // 查询评论用户信息，并设置需要返回的评论用户名
            List<Long> userIds = bookComments.stream().map(BookComment::getUserId).toList();
            List<UserInfo> userInfos = new LambdaQueryChainWrapper<>(userInfoMapper)
                    .in(UserInfo::getId, userIds)
                    .list();
            Map<Long, UserInfo> userInfoMap = userInfos.stream()
                    .collect(Collectors.toMap(UserInfo::getId, Function.identity()));
            List<BookCommentRespDto.CommentInfo> commentInfos = bookComments.stream()
                    .map(v -> BookCommentRespDto.CommentInfo.builder()
                            .id(v.getId())
                            .commentUserId(v.getUserId())
                            .commentUser(userInfoMap.get(v.getUserId()).getUsername())
                            .commentUserPhoto(userInfoMap.get(v.getUserId()).getUserPhoto())
                            .commentContent(v.getCommentContent())
                            .commentTime(v.getCreateTime()).build()).toList();
            bookCommentRespDto.setComments(commentInfos);
        } else {
            bookCommentRespDto.setComments(Collections.emptyList());
        }
        return RestResp.ok(bookCommentRespDto);
    }

    @Override
    public RestResp<Void> saveComment(UserCommentReqDto dto) {
        // 校验用户是否已发表评论
        Long count = new LambdaQueryChainWrapper<>(bookCommentMapper)
                .eq(BookComment::getUserId, dto.getUserId())
                .eq(BookComment::getBookId, dto.getBookId())
                .count();
        if (count > 0) {
            // 用户已发表评论
            return RestResp.fail(ErrorCodeEnum.USER_COMMENTED);
        }
        BookComment bookComment = BeanUtil.copyProperties(dto, BookComment.class);
        bookComment.setCreateTime(LocalDateTime.now());
        bookComment.setUpdateTime(LocalDateTime.now());
        bookCommentMapper.insert(bookComment);
        return RestResp.ok();
    }

    @Override
    public RestResp<Void> updateComment(Long userId, Long id, String content) {
        BookComment bookComment = new BookComment();
        bookComment.setCommentContent(content);
        new LambdaUpdateChainWrapper<>(bookCommentMapper)
                .eq(BookComment::getId, id)
                .eq(BookComment::getUserId, userId)
                .update(bookComment);
        return RestResp.ok();
    }

    @Override
    public RestResp<Void> deleteComment(Long userId, Long id) {
        new LambdaQueryChainWrapper<>(bookCommentMapper)
                .eq(BookComment::getId, id)
                .eq(BookComment::getUserId, userId)
                .clear();
        return RestResp.ok();
    }

    @Override
    public RestResp<Void> addVisitCount(Long bookId) {
        new LambdaUpdateChainWrapper<>(bookInfoMapper)
                .setSql("visit_count = visit_count + 1")
                        .eq(BookInfo::getId, bookId)
                                .update();
        return RestResp.ok();
    }

    @Override
    public RestResp<BookContentAboutRespDto> getBookContentAbout(Long chapterId) {
        // 查询章节信息
        BookChapterRespDto bookChapter = bookChapterCacheManager.getChapter(chapterId);
        // 查询章节内容
        String content = bookContentCacheManager.getBookContent(chapterId);
        // 查询小说信息
        BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(bookChapter.getBookId());

        // 组装数据并返回
        return RestResp.ok(BookContentAboutRespDto.builder()
                .bookInfo(bookInfo)
                .chapterInfo(bookChapter)
                .bookContent(content)
                .build());
    }

    @Override
    public RestResp<String> getPreChapterId(Long chapterId) {
        // 查询小说ID 和 章节号
        BookChapterRespDto chapter = bookChapterCacheManager.getChapter(chapterId);
        Long bookId = chapter.getBookId();
        Integer chapterNum = chapter.getChapterNum();

        // 查询上一章信息并返回章节ID
        BookChapter one = new LambdaQueryChainWrapper<>(bookChapterMapper)
                .eq(BookChapter::getBookId, bookId)
                .lt(BookChapter::getChapterNum, chapterNum)
                .orderByDesc(BookChapter::getChapterNum)
                .last("limit 1")
                .one();
        if (one == null) return RestResp.ok(null);
        return RestResp.ok(String.valueOf(one.getId()));
    }

    @Override
    public RestResp<String> getNextChapterId(Long chapterId) {
        // 查询小说ID 和 章节号
        BookChapterRespDto chapter = bookChapterCacheManager.getChapter(chapterId);
        Long bookId = chapter.getBookId();
        Integer chapterNum = chapter.getChapterNum();

        // 查询下一章信息并返回章节ID
        BookChapter one = new LambdaQueryChainWrapper<>(bookChapterMapper)
                .eq(BookChapter::getBookId, bookId)
                .gt(BookChapter::getChapterNum, chapterNum)
                .orderByAsc(BookChapter::getChapterNum)
                .last("limit 1")
                .one();
        if (one == null) return RestResp.ok(null);
        return RestResp.ok(String.valueOf(one.getId()));
    }

    @Override
    public RestResp<Void> saveBook(BookAddReqDto dto) {
        // 校验小说名是否已存在
        LambdaQueryWrapper<BookInfo> queryWrapper = new LambdaQueryWrapper<BookInfo>()
                .eq(BookInfo::getBookName, dto.getBookName());
        if (bookInfoMapper.selectCount(queryWrapper) > 0) {
            return RestResp.fail(ErrorCodeEnum.AUTHOR_BOOK_NAME_EXIST);
        }
        BookInfo bookInfo = BeanUtil.copyProperties(dto, BookInfo.class);
        // 设置作家信息
        AuthorInfoDto author = authorService.getAuthor(UserHolder.getUserId());
        bookInfo.setAuthorId(author.getId());
        bookInfo.setAuthorName(author.getPenName());
        // 设置其他信息
        bookInfo.setScore(6);
        bookInfo.setCreateTime(LocalDateTime.now());
        bookInfo.setUpdateTime(LocalDateTime.now());
        // 保存小说信息
        bookInfoMapper.insert(bookInfo);
        return RestResp.ok();
    }

    @Override
    public RestResp<PageRespDto<BookInfoRespDto>> listAuthorBooks(PageReqDto dto) {
        IPage<BookInfo> page = new Page<>();
        page.setCurrent(dto.getPageNum());
        page.setSize(dto.getPageSize());
        LambdaQueryWrapper<BookInfo> queryWrapper = new LambdaQueryWrapper<BookInfo>()
                .eq(BookInfo::getAuthorId, UserHolder.getAuthorId())
                .orderByDesc(BookInfo::getCreateTime);
        IPage<BookInfo> bookInfoPage = bookInfoMapper.selectPage(page, queryWrapper);
        return RestResp.ok(PageRespDto.of(dto.getPageNum(), dto.getPageSize(), page.getTotal(),
                bookInfoPage.getRecords()
                        .stream()
                        .map(v -> BeanUtil.copyProperties(v, BookInfoRespDto.class))
                        .toList()));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public RestResp<Void> saveBookChapter(ChapterAddReqDto dto) {
        // 校验该作品是否属于当前作家
        BookInfo bookInfo = bookInfoMapper.selectById(dto.getBookId());
        if (!Objects.equals(bookInfo.getAuthorId(), UserHolder.getAuthorId())) {
            return RestResp.fail(ErrorCodeEnum.USER_UN_AUTH);
        }
        // 1) 保存章节相关信息到小说章节表
        //  a) 查询最新章节号
        int chapterNum = 0;
        LambdaQueryWrapper<BookChapter> chapterQueryWrapper = new LambdaQueryWrapper<BookChapter>()
                .eq(BookChapter::getBookId, dto.getBookId())
                .orderByDesc(BookChapter::getChapterNum)
                .last("limit 1");
        BookChapter bookChapter = bookChapterMapper.selectOne(chapterQueryWrapper);
        if (Objects.nonNull(bookChapter)) {
            chapterNum = bookChapter.getChapterNum() + 1;
        }
        //  b) 设置章节相关信息并保存
        BookChapter newBookChapter = new BookChapter();
        newBookChapter.setBookId(dto.getBookId());
        newBookChapter.setChapterName(dto.getChapterName());
        newBookChapter.setChapterNum(chapterNum);
        newBookChapter.setWordCount(dto.getChapterContent().length());
        newBookChapter.setIsVip(dto.getIsVip());
        newBookChapter.setCreateTime(LocalDateTime.now());
        newBookChapter.setUpdateTime(LocalDateTime.now());
        bookChapterMapper.insert(newBookChapter);

        // 2) 保存章节内容到小说内容表
        BookContent bookContent = new BookContent();
        bookContent.setContent(dto.getChapterContent());
        bookContent.setChapterId(newBookChapter.getId());
        bookContent.setCreateTime(LocalDateTime.now());
        bookContent.setUpdateTime(LocalDateTime.now());
        bookContentMapper.insert(bookContent);

        // 3) 更新小说表最新章节信息和小说总字数信息
        //  a) 更新小说表关于最新章节的信息
        BookInfo newBookInfo = new BookInfo();
        newBookInfo.setId(dto.getBookId());
        newBookInfo.setLastChapterId(newBookChapter.getId());
        newBookInfo.setLastChapterName(newBookChapter.getChapterName());
        newBookInfo.setLastChapterUpdateTime(LocalDateTime.now());
        newBookInfo.setWordCount(bookInfo.getWordCount() + newBookChapter.getWordCount());
        newBookChapter.setUpdateTime(LocalDateTime.now());
        bookInfoMapper.updateById(newBookInfo);
        //  b) 清除小说信息缓存
        clearCache.evictBookChapterCache(
                CAFFEINE_BOOK_INFO +  dto.getBookId(),
                REDIS_BOOK_INFO +  dto.getBookId());
        //   c) 发送小说信息更新的 MQ 消息
        amqpMsgManager.sendBookChangeMsg(dto.getBookId());
        return RestResp.ok();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public RestResp<Void> deleteBookChapter(Long chapterId) {
        // 1.查询章节信息
        BookChapterRespDto chapter = bookChapterCacheManager.getChapter(chapterId);
        // 2.查询小说信息
        BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(chapter.getBookId());
        // 3.删除章节信息
        bookChapterMapper.deleteById(chapterId);
        // 4.删除章节内容
        new LambdaUpdateChainWrapper<>(bookContentMapper)
                .eq(BookContent::getChapterId, chapterId)
                .remove();
        // 5.更新小说信息
        BookInfo newBookInfo = new BookInfo();
        newBookInfo.setId(chapter.getBookId());
        newBookInfo.setUpdateTime(LocalDateTime.now());
        newBookInfo.setWordCount(bookInfo.getWordCount() - chapter.getChapterWordCount());
        if (Objects.equals(bookInfo.getLastChapterId(), chapterId)) {
            // 设置最新章节信息
            BookChapter bookChapter = new LambdaQueryChainWrapper<>(bookChapterMapper)
                    .eq(BookChapter::getBookId, chapter.getBookId())
                    .orderByDesc(BookChapter::getChapterNum)
                    .last("limit 1")
                    .one();
            Long lastChapterId = 0L;
            String lastChapterName = "";
            LocalDateTime lastChapterUpdateTime = null;
            if (Objects.nonNull(bookChapter)) {
                lastChapterId = bookChapter.getId();
                lastChapterName = bookChapter.getChapterName();
                lastChapterUpdateTime = bookChapter.getUpdateTime();
            }
            newBookInfo.setLastChapterId(lastChapterId);
            newBookInfo.setLastChapterName(lastChapterName);
            newBookInfo.setLastChapterUpdateTime(lastChapterUpdateTime);
        }
        bookInfoMapper.updateById(newBookInfo);
        //  6.清理章节信息缓存
        clearCache.evictBookChapterCache(
                CAFFEINE_BOOK_CHAPTER + chapterId,
                REDIS_BOOK_CHAPTER + chapterId);
        // 7.清理章节内容缓存
        clearCache.evictBookChapterCache(
                CAFFEINE_BOOK_CONTENT + chapterId,
                REDIS_BOOK_CONTENT + chapterId);
        // 8.清理小说信息缓存
        clearCache.evictBookChapterCache(
                CAFFEINE_BOOK_INFO + chapter.getBookId(),
                REDIS_BOOK_INFO + chapter.getBookId());
        //  9.发送小说信息更新的 MQ 消息
        amqpMsgManager.sendBookChangeMsg(chapter.getBookId());
        return RestResp.ok();
    }

    @Override
    public RestResp<ChapterContentRespDto> getBookChapter(Long chapterId) {
        BookChapterRespDto chapter = bookChapterCacheManager.getChapter(chapterId);
        String bookContent = bookContentCacheManager.getBookContent(chapterId);
        return RestResp.ok(
                ChapterContentRespDto.builder()
                        .chapterName(chapter.getChapterName())
                        .chapterContent(bookContent)
                        .isVip(chapter.getIsVip())
                        .build());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public RestResp<Void> updateBookChapter(Long chapterId, ChapterUpdateReqDto dto) {
        // 1.查询章节信息
        BookChapterRespDto chapter = bookChapterCacheManager.getChapter(chapterId);
        // 2.查询小说信息
        BookInfoRespDto bookInfo = bookInfoCacheManager.getBookInfo(chapter.getBookId());
        // 3.更新章节信息
        BookChapter newChapter = new BookChapter();
        newChapter.setId(chapterId);
        newChapter.setChapterName(dto.getChapterName());
        newChapter.setWordCount(dto.getChapterContent().length());
        newChapter.setIsVip(dto.getIsVip());
        newChapter.setUpdateTime(LocalDateTime.now());
        bookChapterMapper.updateById(newChapter);
        // 4.更新章节内容
        BookContent newContent = new BookContent();
        newContent.setContent(dto.getChapterContent());
        newContent.setUpdateTime(LocalDateTime.now());
        new LambdaUpdateChainWrapper<>(bookContentMapper)
                .eq(BookContent::getChapterId, chapterId)
                        .update(newContent);
        // 5.更新小说信息
        BookInfo newBookInfo = new BookInfo();
        newBookInfo.setId(chapter.getBookId());
        newBookInfo.setUpdateTime(LocalDateTime.now());
        newBookInfo.setWordCount(
                bookInfo.getWordCount() - chapter.getChapterWordCount() + dto.getChapterContent().length());
        if (Objects.equals(bookInfo.getLastChapterId(), chapterId)) {
            // 更新最新章节信息
            newBookInfo.setLastChapterName(dto.getChapterName());
            newBookInfo.setLastChapterUpdateTime(LocalDateTime.now());
        }
        bookInfoMapper.updateById(newBookInfo);
        //  6.清理章节信息缓存
        clearCache.evictBookChapterCache(
                CAFFEINE_BOOK_CHAPTER + chapterId,
                REDIS_BOOK_CHAPTER + chapterId);
        // 7.清理章节内容缓存
        clearCache.evictBookChapterCache(
                CAFFEINE_BOOK_CONTENT + chapterId,
                REDIS_BOOK_CONTENT + chapterId);
        // 8.清理小说信息缓存
        clearCache.evictBookChapterCache(
                CAFFEINE_BOOK_INFO + chapter.getBookId(),
                REDIS_BOOK_INFO + chapter.getBookId());
        //   9.发送小说信息更新的 MQ 消息
        amqpMsgManager.sendBookChangeMsg(chapter.getBookId());
        return RestResp.ok();
    }

    @Override
    public RestResp<PageRespDto<BookChapterRespDto>> listBookChapters(Long bookId, PageReqDto dto) {
        IPage<BookChapter> page = new Page<>();
        page.setCurrent(dto.getPageNum());
        page.setSize(dto.getPageSize());
        LambdaQueryWrapper<BookChapter> queryWrapper = new LambdaQueryWrapper<BookChapter>()
                .eq(BookChapter::getBookId, bookId)
                .orderByDesc(BookChapter::getChapterNum);
        IPage<BookChapter> bookChapterPage = bookChapterMapper.selectPage(page, queryWrapper);
        return RestResp.ok(PageRespDto.of(dto.getPageNum(), dto.getPageSize(), page.getTotal(),
                bookChapterPage.getRecords()
                        .stream()
                        .map(v -> {
                            BookChapterRespDto bookChapterRespDto =
                                    BeanUtil.copyProperties(v, BookChapterRespDto.class);
                            bookChapterRespDto.setChapterUpdateTime(v.getUpdateTime());
                            bookChapterRespDto.setChapterWordCount(v.getWordCount());
                            return bookChapterRespDto;
                        })
                        .toList()));
    }


    private List<BookRankRespDto> listRankBooks(String caffeineKey, String redisKey, long redisTTL,
                                                LambdaQueryWrapper<BookInfo> bookInfoQueryWrapper) {
        List<BookRankRespDto> cache = cacheUtils.getCache(
                obj -> BeanUtil.copyProperties(obj, BookRankRespDto.class),
                caffeineKey, redisKey,
                jsonStr -> JSONUtil.toBean(jsonStr, BookRankRespDto.class));
        if (cache != null) return cache;
        bookInfoQueryWrapper
                .gt(BookInfo::getWordCount, 0)
                .last("limit 30");
        List<BookRankRespDto> list = BeanUtil.copyToList(
                bookInfoMapper.selectList(bookInfoQueryWrapper),
                BookRankRespDto.class);
        cacheUtils.saveCache(list, caffeineKey,
                redisKey, redisTTL, JSONUtil::toJsonStr);
        return list;
    }


}
